package logger

import (
	"fmt"
	"path"
	"runtime"
	"strconv"
	"strings"
	"time"
)

type Level int

const (
	DebugLevel = Level(iota)
	WarnLevel
	ErrorLevel
)

var (
	defaultLogger *Logger
	Stdout        *StdOutput
)

type LogOutput interface {
	Output(level Level, s string)
	Flush()
	Close()
}

type Logger struct {
	printHeader bool
	level       Level
	outputs     []LogOutput
	callerDepth int
}

func (l *Logger) SetLevel(level Level) {
	l.level = level
}

func (l *Logger) AddOutput(output LogOutput) {
	l.outputs = append(l.outputs, output)
}

func (l *Logger) ClearOutputs() {
	for i := 0; i < len(l.outputs); i++ {
		l.outputs[i].Close()
	}

	l.outputs = make([]LogOutput, 0)
}

func (l *Logger) output(level Level, logHeader, format string, v ...interface{}) {
	s := logHeader + fmt.Sprintf(format, v...) + "\n"
	for i := 0; i < len(l.outputs); i++ {
		l.outputs[i].Output(level, s)
	}
}

func (l *Logger) sysOutput(level Level, logHeader, format string, v ...interface{}) {
	s := logHeader + fmt.Sprintf(format, v...) + "\n"

	hasStdout := false
	for i := 0; i < len(l.outputs); i++ {
		if l.outputs[i] == Stdout {
			hasStdout = true
		}
		l.outputs[i].Output(level, s)
	}

	if !hasStdout {
		Stdout.Output(level, s)
	}
}

func (l *Logger) genLogHeader(tag string, depth int) string {
	builder := strings.Builder{}
	builder.WriteString(tag)
	builder.WriteByte(' ')
	builder.WriteString(time.Now().Format("20060102-15:04:05"))
	builder.WriteByte(' ')

	_, file, line, ok := runtime.Caller(depth)
	if !ok {
		file = "???"
		line = 0
	}

	builder.WriteString(path.Base(file))
	builder.WriteByte(':')
	builder.WriteString(strconv.Itoa(line))
	builder.WriteByte(' ')
	return builder.String()
}

func (l *Logger) SysDebug(format string, v ...interface{}) {
	logHeader := ""
	if l.printHeader {
		logHeader = l.genLogHeader("Debug", l.callerDepth)
	}
	l.sysOutput(DebugLevel, logHeader, format, v...)
}

func (l *Logger) SysError(format string, v ...interface{}) {
	logHeader := ""
	if l.printHeader {
		logHeader = l.genLogHeader("Error", l.callerDepth)
	}
	l.sysOutput(ErrorLevel, logHeader, format, v...)
}

func (l *Logger) Debug(format string, v ...interface{}) {
	if l.level > DebugLevel {
		return
	}
	logHeader := ""
	if l.printHeader {
		logHeader = l.genLogHeader("Debug", l.callerDepth)
	}
	l.output(DebugLevel, logHeader, format, v...)
}

func (l *Logger) Warn(format string, v ...interface{}) {
	if l.level > WarnLevel {
		return
	}
	logHeader := ""
	if l.printHeader {
		logHeader = l.genLogHeader("Warn", l.callerDepth)
	}
	l.output(WarnLevel, logHeader, format, v...)
}

func (l *Logger) Error(format string, v ...interface{}) {
	if l.level > ErrorLevel {
		return
	}
	logHeader := ""
	if l.printHeader {
		logHeader = l.genLogHeader("Error", l.callerDepth)
	}
	l.output(ErrorLevel, logHeader, format, v...)
}

func (l *Logger) Flush() {

	for i := 0; i < len(l.outputs); i++ {
		l.outputs[i].Flush()
	}
}

func (l *Logger) Close() {
	for i := 0; i < len(l.outputs); i++ {
		l.outputs[i].Close()
	}
}

func NewLogger(printHeader bool) *Logger {
	return &Logger{
		printHeader: printHeader,
		outputs:     make([]LogOutput, 0),
		callerDepth: 2,
	}
}

func SetLevel(level Level) {
	defaultLogger.SetLevel(level)
}

func AddOutput(output LogOutput) {
	defaultLogger.AddOutput(output)
}

func ClearOutputs() {
	defaultLogger.ClearOutputs()
}

func SysDebug(format string, v ...interface{}) {
	defaultLogger.SysDebug(format, v...)
}

func SysError(format string, v ...interface{}) {
	defaultLogger.SysError(format, v...)
}

func Debug(format string, v ...interface{}) {
	defaultLogger.Debug(format, v...)
}

func Warn(format string, v ...interface{}) {
	defaultLogger.Warn(format, v...)
}

func Error(format string, v ...interface{}) {
	defaultLogger.Error(format, v...)
}

func Flush() {
	defaultLogger.Flush()
}

func Close() {
	defaultLogger.Flush()
}

func init() {
	defaultLogger = NewLogger(true)
	defaultLogger.callerDepth = 3
	defaultLogger.SetLevel(DebugLevel)
	Stdout = NewStdOutput()
	defaultLogger.AddOutput(Stdout)
}
